#include "CURLHttpConnection.h"

LIB_UTIL_NAMESPACE_BEGIN

#define DEFAULT_BUF_SIZE		4096
#ifdef _DEBUG
#define DEFAULT_TIMEOUT			60
#else
#define DEFAULT_TIMEOUT			5
#endif

CURLConnectionManager::CURLConnectionManager(void)
{
	curl_global_init(CURL_GLOBAL_DEFAULT);
}

CURLConnectionManager::~CURLConnectionManager(void)
{
	curl_global_cleanup();
}

bool CURLConnectionManager::Initialize()
{
	return true;
}

bool CURLConnectionManager::Unitialize()
{
	return true;
}

HttpConnection* CURLConnectionManager::OpenConnection(tConnectionData& data)
{
	CURLHttpConnection* connection = new CURLHttpConnection(data);
	if(!connection->Open())
	{
		delete connection;
		return NULL;
	}
	return connection;
}

CURLHttpConnection::CURLHttpConnection(tConnectionData& data)
:m_data(data)
{
	m_curlContext = NULL;
	m_buffer = NULL;
	m_eventHandler = NULL;
	m_ret = false;
}

CURLHttpConnection::~CURLHttpConnection(void)
{
	Close();
}

bool CURLHttpConnection::Open()
{
	if(m_curlContext != NULL)
		return false;

	if(m_data.outData == NULL)
		return false;

	if(m_data.type == tConnectionData::POST && 
		(m_data.inData == NULL || m_data.inData->data == NULL))
		return false;

	m_curlContext = curl_easy_init();

	int timeout = 0;
	if(m_data.timeout <= 0)
		timeout = DEFAULT_TIMEOUT;
	else
		timeout = m_data.timeout;
#ifdef _WINDOWS
	// On linux, curl can not handle timeout well in multithread environment.
	curl_easy_setopt(m_curlContext, CURLOPT_TIMEOUT, timeout);
#endif
	curl_easy_setopt(m_curlContext, CURLOPT_URL, m_data.url);
	curl_easy_setopt(m_curlContext, CURLOPT_PORT, m_data.port);
	if(m_data.type == tConnectionData::POST)
	{
		curl_easy_setopt(m_curlContext, CURLOPT_POSTFIELDS, m_data.inData->data);
		curl_easy_setopt(m_curlContext, CURLOPT_POSTFIELDSIZE, m_data.inData->size);
		curl_easy_setopt(m_curlContext, CURLOPT_POST, true);
	}
	else
		curl_easy_setopt(m_curlContext, CURLOPT_HTTPGET, true);

#ifdef _WINDOWS
	curl_easy_setopt(m_curlContext, CURLOPT_SOCKOPTFUNCTION, setsockoptcallback);
#endif

	curl_easy_setopt(m_curlContext, CURLOPT_SSL_CTX_FUNCTION, setsslctxcallback);

	m_buffer = new tDataBuffer;
	m_buffer->data = new byte[DEFAULT_BUF_SIZE];
	memset(m_buffer->data, 0, DEFAULT_BUF_SIZE);
	m_buffer->pos = 0;
	m_buffer->size = DEFAULT_BUF_SIZE;

	curl_easy_setopt(m_curlContext, CURLOPT_WRITEFUNCTION, write);
	curl_easy_setopt(m_curlContext, CURLOPT_WRITEDATA, m_buffer);
	curl_easy_setopt(m_curlContext, CURLOPT_NOPROGRESS, 0);
	curl_easy_setopt(m_curlContext, CURLOPT_PROGRESSFUNCTION, curl_progress_callback);
	curl_easy_setopt(m_curlContext, CURLOPT_PROGRESSDATA, this);

	return true;
}

bool CURLHttpConnection::Connect()
{
	if(m_curlContext == NULL)
		return false;

	m_ret = PerformConnection();
	return m_ret;
}

void CURLHttpConnection::SetEventHandler(ConnectionEventHandler* handler)
{
	m_eventHandler = handler;
}

void CURLHttpConnection::Cancel()
{
}

bool CURLHttpConnection::Close()
{
	if(m_curlContext == NULL)
		return false;
	
	curl_easy_cleanup(m_curlContext);
	m_curlContext = NULL;
	ReleaseBuffer();
	return true;
}

bool CURLHttpConnection::Destroy()
{
	delete this;
	return true;
}

bool CURLHttpConnection::PerformConnection()
{
	struct curl_slist *slist=NULL;
	curl_easy_setopt(m_curlContext, CURLOPT_HTTPHEADER, slist);
	curl_easy_setopt(m_curlContext, CURLOPT_SSL_VERIFYPEER, 0);
	curl_easy_setopt(m_curlContext, CURLOPT_SSL_VERIFYHOST, 0);

	int ret = curl_easy_perform(m_curlContext);
	if(ret == 0)
	{
		m_data.outData->data = new byte[m_buffer->pos];
		memset(m_data.outData->data, 0, m_buffer->pos);
		m_data.outData->size = m_buffer->pos;
		memcpy(m_data.outData->data, m_buffer->data, m_data.outData->size);
		if(m_eventHandler != NULL)
			m_eventHandler->OnSuccess(this);
	}
	else
	{
		if(m_eventHandler != NULL)
			m_eventHandler->OnFailed(this, ret);
	}
	curl_slist_free_all(slist);
	return ret == 0;
}

void CURLHttpConnection::ReleaseBuffer()
{
	if(m_buffer == NULL)
		return;

	SAFE_DELETE_ARRAY(m_buffer->data);
	SAFE_DELETE(m_buffer);
}

#ifdef _WINDOWS
int CURLHttpConnection::setsockoptcallback(void* clientp, curl_socket_t curlfd, curlsocktype purpose)
{
	struct linger li;   
	li.l_onoff   =   1;   
	li.l_linger   =   0;   
	return setsockopt(curlfd, SOL_SOCKET, SO_LINGER, (char*)&li, sizeof(li)); 
}
#endif

int CURLHttpConnection::setsslctxcallback(CURL* curl, void* sslctx, void* parm)
{
	return 0;
}

size_t CURLHttpConnection::write(void* ptr, size_t size, size_t nmemb, void* stream)
{
	if(ptr == NULL || stream == NULL)
		return 0;

	tDataBuffer* buffer = (tDataBuffer*)stream;
	size_t length = size * nmemb;
	if(buffer->data == NULL)
	{
		buffer->size = length;
		buffer->data = new byte[buffer->size];
		buffer->pos = 0;
	}

	if(buffer->pos + length  > buffer->size)
	{
		size_t size = buffer->pos + length * 2;
		byte* tmp = new byte[size];
		memcpy(tmp, buffer->data, buffer->pos);
		SAFE_DELETE_ARRAY(buffer->data);
		buffer->data = tmp;
		buffer->size = size;
	}
	memcpy(buffer->data + buffer->pos, ptr, length);
	buffer->pos += length;
	return length;
}

int CURLHttpConnection::curl_progress_callback(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow)
{
	CURLHttpConnection* connection = (CURLHttpConnection*)clientp;
	if(connection->m_eventHandler == NULL)
		return 0;

	connection->m_eventHandler->OnProgress(connection, dltotal, dlnow);
	return 0;
}

LIB_UTIL_NAMESPACE_END

